﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DirectX;
using BurningSphere.Simulator.Properties;
using Microsoft.DirectX.Direct3D;
using System.Diagnostics;
using System.Drawing;
using System.Xml;
using BurningSphere.Simulator.Controls;
using System.Reflection;
using BurningSphere.Simulator.MIDI;
using Sanford.Multimedia.Midi;

namespace BurningSphere.Simulator
{
	/// <summary>
	/// Laser object.  Intended to manage a single laser.  Each laser has a matrix
	/// which orientates and positions it.  It also has a velocity and acceleration matrix
	/// for simulating wind-up and wind-down speed in the real world laser.  None
	/// of that is simulated, and would require a sub-class.
	/// 
	/// Each laser object manages a history of position points which it fades the colour
	/// and alpha of until they're invisible (and fall off the end of the history queue.
	/// 
	/// The laser object also keeps a control queue of LaserControl objects.  These are
	/// used for affecting the behaviour of the laser, valid LaserControl objects are:
	///		ShapeProjection - plays back shape data
	///		LaserPause - stops the laser for a period of time
	///		
	/// </summary>
	public class Laser
	{
		List<MidiProperty> midiProperties = new List<MidiProperty>();

		protected Matrix matrix;
		protected Matrix velocityMatrix;
		protected Matrix accelerationMatrix;
		protected Material laserMaterial;
		protected int historyCapacity = 1000;
		protected Queue<LaserHistory> history = new Queue<LaserHistory>(1000);
		protected Queue<LaserControl> control = new Queue<LaserControl>();
		protected LaserControl currentControl = null;
		protected Line line;
		protected float lineWidth = 2f;
		protected ColorValue initialColour;
		protected ColorValue fadeScalar;
		protected Material whiteMaterial;

		float? scale;
		float? rotateX;
		float? rotateY;
		float? rotateZ;
		float? velocityX;
		float? velocityY;
		float? velocityZ;
		float? accelX;
		float? accelY;
		float? accelZ;

		VertexBuffer testQuad;

		/// <summary>
		/// Ctor
		/// </summary>
		public Laser()
		{
			matrix = Matrix.Identity;
			velocityMatrix = Matrix.Identity;
			accelerationMatrix = Matrix.Identity;
		}

		/// <summary>
		/// Width of the laser
		/// </summary>
		public float LineWidth
		{
			get
			{
				return lineWidth;
			}
			set
			{
				lineWidth = value;
			}
		}
		/// <summary>
		/// Scale
		/// </summary>
		public float Scale
		{
			get
			{
				return scale.GetValueOrDefault();
			}
			set
			{
				if (scale != value)
				{
					scale = value;
					UpdateCoreMatrix();
				}
			}
		}
		/// <summary>
		/// RotateX
		/// </summary>
		public float RotateX
		{
			get
			{
				return rotateX.GetValueOrDefault();
			}
			set
			{
				if (rotateX != value)
				{
					if (rotateX != null)
					{
						matrix *= Matrix.RotationX(Geometry.DegreeToRadian(-rotateX.Value));
					}
					rotateX = value;
					matrix *= Matrix.RotationX(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// RotateY
		/// </summary>
		public float RotateY
		{
			get
			{
				return rotateY.GetValueOrDefault();
			}
			set
			{
				if (rotateY != value)
				{
					if (rotateY != null)
					{
						matrix *= Matrix.RotationY(Geometry.DegreeToRadian(-rotateY.Value));
					}
					rotateY = value;
					matrix *= Matrix.RotationY(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// RotateZ
		/// </summary>
		public float RotateZ
		{
			get
			{
				return rotateZ.GetValueOrDefault();
			}
			set
			{
				if (rotateZ != value)
				{
					if (rotateZ != null)
					{
						matrix *= Matrix.RotationZ(Geometry.DegreeToRadian(-rotateZ.Value));
					}
					rotateZ = value;
					matrix *= Matrix.RotationZ(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// VelocityX
		/// </summary>
		public float VelocityX
		{
			get
			{
				return velocityX.GetValueOrDefault();
			}
			set
			{
				if (velocityX != value)
				{
					if (velocityX != null)
					{
						velocityMatrix *= Matrix.RotationX(Geometry.DegreeToRadian(-velocityX.Value));
					}
					velocityX = value;
					velocityMatrix *= Matrix.RotationX(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// VelocityY
		/// </summary>
		public float VelocityY
		{
			get
			{
				return velocityY.GetValueOrDefault();
			}
			set
			{
				if (velocityY != value)
				{
					if (velocityY != null)
					{
						velocityMatrix *= Matrix.RotationY(Geometry.DegreeToRadian(-velocityY.Value));
					}
					velocityY = value;
					velocityMatrix *= Matrix.RotationY(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// VelocityZ
		/// </summary>
		public float VelocityZ
		{
			get
			{
				return velocityZ.GetValueOrDefault();
			}
			set
			{
				if (velocityZ != value)
				{
					if (velocityZ != null)
					{
						velocityMatrix *= Matrix.RotationZ(Geometry.DegreeToRadian(-velocityZ.Value));
					}
					velocityZ = value;
					velocityMatrix *= Matrix.RotationZ(Geometry.DegreeToRadian(value));
				}
			}
		}
		/// <summary>
		/// AccelX
		/// </summary>
		public float AccelX
		{
			get
			{
				return accelX.GetValueOrDefault();
			}
			set
			{
				if (accelX != value)
				{
					accelX = value;
					UpdateAccelerationMatrix();
				}
			}
		}
		/// <summary>
		/// AccelY
		/// </summary>
		public float AccelY
		{
			get
			{
				return accelY.GetValueOrDefault();
			}
			set
			{
				if (accelY != value)
				{
					accelY = value;
					UpdateAccelerationMatrix();
				}
			}
		}
		/// <summary>
		/// AccelZ
		/// </summary>
		public float AccelZ
		{
			get
			{
				return accelZ.GetValueOrDefault();
			}
			set
			{
				if (accelZ != value)
				{
					accelZ = value;
					UpdateAccelerationMatrix();
				}
			}
		}

		/// <summary>
		/// Rotational velocity matrix
		/// </summary>
		public Matrix VelocityMatrix
		{
			get
			{
				return velocityMatrix;
			}
			set
			{
				velocityMatrix = value;
			}
		}

		/// <summary>
		/// Rotational acceleration matrix
		/// </summary>
		public Matrix AccelerationMatrix
		{
			get
			{
				return accelerationMatrix;
			}
			set
			{
				accelerationMatrix = value;
			}
		}

		/// <summary>
		/// History of laser positions and colours
		/// </summary>
		public Queue<LaserHistory> History
		{
			get
			{
				return history;
			}
		}

		/// <summary>
		/// Queue of control objects
		/// </summary>
		public Queue<LaserControl> Controls
		{
			get
			{
				return control;
			}
		}

		/// <summary>
		/// Initial colour of the laser
		/// </summary>
		public ColorValue InitialColour
		{
			get
			{
				return initialColour;
			}
			set
			{
				initialColour = value;
			}
		}

		/// <summary>
		/// Value used to scale the laser colour
		/// </summary>
		public ColorValue FadeScalar
		{
			get
			{
				return fadeScalar;
			}
			set
			{
				fadeScalar = value;
			}
		}

		/// <summary>
		/// UpdateMatrices
		/// </summary>
		private void UpdateCoreMatrix()
		{
			matrix = Matrix.Identity;
			Matrix rotate = Matrix.Identity;
			if (scale != null)
			{
				rotate = Matrix.Scaling(scale.Value, scale.Value, scale.Value);
				matrix *= rotate;
			}
			if (rotateX != null)
			{
				rotate = Matrix.RotationX(Geometry.DegreeToRadian(rotateX.Value));
				matrix *= rotate;
			}
			if (rotateY != null)
			{
				rotate = Matrix.RotationY(Geometry.DegreeToRadian(rotateY.Value));
				matrix *= rotate;
			}
			if (rotateZ != null)
			{
				rotate = Matrix.RotationZ(Geometry.DegreeToRadian(rotateZ.Value));
				matrix *= rotate;
			}
		}

		/// <summary>
		/// Update velocity matrix
		/// </summary>
		private void UpdateVelocityMatrix()
		{
			Matrix matrix;
			velocityMatrix = Matrix.Identity;
			if (velocityX != null)
			{
				matrix = Matrix.RotationX(Geometry.DegreeToRadian(velocityX.Value));
				velocityMatrix *= matrix;
			}
			if (velocityY != null)
			{
				matrix = Matrix.RotationY(Geometry.DegreeToRadian(velocityY.Value));
				velocityMatrix *= matrix;
			}
			if (velocityZ != null)
			{
				matrix = Matrix.RotationZ(Geometry.DegreeToRadian(velocityZ.Value));
				velocityMatrix *= matrix;
			}

		}

		/// <summary>
		/// Update acceleration matrix
		/// </summary>
		private void UpdateAccelerationMatrix()
		{
			Matrix matrix;
			accelerationMatrix = Matrix.Identity;
			if (accelX != null)
			{
				matrix = Matrix.RotationX(Geometry.DegreeToRadian(accelX.Value));
				accelerationMatrix *= matrix;
			}
			if (accelY != null)
			{
				matrix = Matrix.RotationY(Geometry.DegreeToRadian(accelY.Value));
				accelerationMatrix *= matrix;
			}
			if (accelZ != null)
			{
				matrix = Matrix.RotationZ(Geometry.DegreeToRadian(accelZ.Value));
				accelerationMatrix *= matrix;
			}
		}

		/// <summary>
		/// Graphics initialise
		/// </summary>
		/// <param name="device"></param>
		public void Initialise(Device device)
		{
			if (line != null)
			{
				line.Dispose();
			}

			if (testQuad != null)
			{
				testQuad.Dispose();
			}

			line = new Line(device);
			line.Antialias = true;

			laserMaterial = new Material();
			laserMaterial.Ambient = Color.FromArgb(255, 255, 255, 255);
			laserMaterial.Diffuse = laserMaterial.Ambient;

			whiteMaterial = new Material();
			whiteMaterial.Ambient = Color.FromArgb(255, 128, 255, 255);
			whiteMaterial.Diffuse = whiteMaterial.Ambient;
			
			//create quad
			int colour = Color.Purple.ToArgb();
			CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[6];
			verts[0] = new CustomVertex.PositionColored(-1f, 1f, 0f, colour);
			verts[1] = new CustomVertex.PositionColored(1f, 1f, 0f, colour);
			verts[2] = new CustomVertex.PositionColored(-1f, -1f, 0f, colour);
			verts[3] = new CustomVertex.PositionColored(1f, -1f, 0f, colour);
			verts[4] = new CustomVertex.PositionColored(-1f, -2f, 0f, colour);
			verts[5] = new CustomVertex.PositionColored(1f, -2f, 0f, colour);
			testQuad = new VertexBuffer(
				typeof(CustomVertex.PositionColored), 6, device, 0,
				CustomVertex.PositionColored.Format, Pool.Default
				);
			GraphicsStream stm = testQuad.Lock(0, 0, 0);
			stm.Write(verts);
			testQuad.Unlock();

		}

		/// <summary>
		/// Graphics device lost handler
		/// </summary>
		public void OnLostDevice()
		{
			if (line != null)
			{
				line.Dispose();
				line = null;
			}
		}

		/// <summary>
		/// Shutdown
		/// </summary>
		public void Shutdown()
		{
			if (line != null)
			{
				line.Dispose();
				line = null;
			}
		}

		/// <summary>
		/// Load from a descriptor element
		/// </summary>
		/// <param name="element"></param>
		public void LoadFromXmlElement(XmlElement element)
		{
			Debug.Assert(element.Name == "Laser");

			if (element["InitialColour"] != null)
			{
				string[] parts = element["InitialColour"].InnerText.Split(',');
				Debug.Assert(parts.Length == 3);
				InitialColour = new ColorValue(Single.Parse(parts[0].Trim()), Single.Parse(parts[1].Trim()), Single.Parse(parts[2].Trim()));
			}
			if (element["FadeScalar"] != null)
			{
				string[] parts = element["FadeScalar"].InnerText.Split(',');
				Debug.Assert(parts.Length == 4);
				FadeScalar = new ColorValue(Single.Parse(parts[0].Trim()), Single.Parse(parts[1].Trim()), Single.Parse(parts[2].Trim()), Single.Parse(parts[3].Trim()));
			}
			if (element["HistoryCapacity"] != null)
			{
				historyCapacity = Int32.Parse(element["HistoryCapacity"].InnerText.Trim());
				history = new Queue<LaserHistory>(historyCapacity);
			}
			matrix = Matrix.Identity;
			Matrix rotate = Matrix.Identity;
			if (element["Scale"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("Scale"), element["Scale"].InnerText)
					);
			} 
			if (element["RotateX"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("RotateX"), element["RotateX"].InnerText)
					);
			}
			if (element["RotateY"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("RotateY"), element["RotateY"].InnerText)
					);
			}
			if (element["RotateZ"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("RotateZ"), element["RotateZ"].InnerText)
					);
			}
			velocityMatrix = Matrix.Identity;
			if (element["VelocityX"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("VelocityX"), element["VelocityX"].InnerText)
					);
			}
			if (element["VelocityY"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("VelocityY"), element["VelocityY"].InnerText)
					);
			}
			if (element["VelocityZ"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("VelocityZ"), element["VelocityZ"].InnerText)
					);
			}
			accelerationMatrix = Matrix.Identity;
			if (element["AccelerateX"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("AccelerateX"), element["AccelerateX"].InnerText)
					);
			}
			if (element["AccelerateY"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("AccelerateY"), element["AccelerateY"].InnerText)
				);
			}
			if (element["AccelerateZ"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("AccelerateZ"), element["AccelerateZ"].InnerText)
				);
			}
			if (element["Width"] != null)
			{
				AddProperty(
					MidiProperty.BuildPropertyFromScript(this, this.GetType().GetProperty("LineWidth"), element["Width"].InnerText)
				);

			}

			UpdateCoreMatrix();
			UpdateVelocityMatrix();
			UpdateAccelerationMatrix();

			if (element["Controls"] != null)
			{
				this.Controls.Clear();

				XmlDocument controlClasses = new XmlDocument();
				controlClasses.Load(@"Data\ControlAliases.xml");

				Dictionary<string, string> classes = new Dictionary<string, string>();
				foreach (XmlElement classElement in controlClasses.DocumentElement)
				{
					classes.Add(classElement.Name, classElement.InnerText);
				}

				foreach (XmlElement controlElement in element["Controls"])
				{
					LaserControl control = (LaserControl)Assembly.GetExecutingAssembly().CreateInstance(classes[controlElement.Name]);
					foreach (XmlAttribute attribute in controlElement.Attributes)
					{
						PropertyInfo prop = control.GetType().GetProperty(attribute.Name);
						if (prop == null)
						{
							throw new Exception("Attribute " + attribute.Name + " is not a valid for " + controlElement.Name);
						}

						AddProperty(
							MidiProperty.BuildPropertyFromScript(control, prop, attribute.Value)
							);
					}
					this.Controls.Enqueue(control);
					control.OnLoad();
				}
			}

			foreach (MidiProperty property in midiProperties)
			{
				ChannelMessageEventArgs args = new ChannelMessageEventArgs(new ChannelMessage(ChannelCommand.ProgramChange, 0, 0));
				property.ChannelMessageReceived(this, args);
			}

		}

		/// <summary>
		/// Add property
		/// </summary>
		/// <param name="property"></param>
		private void AddProperty(MidiProperty property)
		{
			if (property == null)
			{
				return;
			}
			midiProperties.Add(property);
		}

		/// <summary>
		/// Update method - Runs the control queue.
		/// </summary>
		public void Update()
		{
			velocityMatrix *= accelerationMatrix;
			matrix *= velocityMatrix;

			float x=0, y=0;
			if (Controls.Count == 0 && currentControl == null)
			{
				Vector2 uv = GetUV(matrix);
				x = (uv.X * 2.0f) - 1.0f;
				y = (uv.Y * 2.0f) - 1.0f;
			}
			else
			{
				if (currentControl == null)
				{
					currentControl = Controls.Dequeue();
				}
				
				if (currentControl.MoveNext())
				{
					currentControl.Update(matrix);
				}
				else
				{
					currentControl = null;
					return;
				}

				if (currentControl.LaserStatus == false)
				{
					return;
				}

				// Find the UV position for the intersection of the matrix look with the
				// sphere
				Vector2 uv = GetUV(currentControl.LaserMatrix);
				x = (uv.X * 2.0f) - 1.0f;
				y = (uv.Y * 2.0f) - 1.0f;
			}

			history.Enqueue(new LaserHistory(x, y,InitialColour));

			if (history.Count >= historyCapacity)
			{
				history.Dequeue();
			}

			Queue<LaserHistory>.Enumerator iter = history.GetEnumerator();
			while (iter.MoveNext())
			{
				iter.Current.Fade(fadeScalar);
			}

		}
		
		public void RenderFast(Device device)
		{
			if (history.Count < 2)
			{
				return;
			}

			//create quad
			CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[ history.Count*2 ];

			device.RenderState.AlphaBlendEnable = true;
			device.RenderState.SourceBlend = Blend.One;
			device.RenderState.DestinationBlend = Blend.One;
			device.RenderState.BlendOperation = BlendOperation.Add;
			device.RenderState.CullMode = Cull.None;


			// quad
	//		device.SetTexture(0, null);
	//		device.Transform.World = Matrix.Identity;
	//		device.Material = whiteMaterial;
	//		device.VertexFormat = CustomVertex.PositionColored.Format;
	//		device.SetStreamSource(0, testQuad, 0);
	//		device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 4);


			int index = 0;
			int nextColour = 0;
			int lastColour = 0;
			bool lastWasFirst = false;
			Queue<LaserHistory>.Enumerator iter = history.GetEnumerator();
			LaserHistory last = null;

			Vector3 lastPos0 = Vector3.Empty;
			Vector3 lastPos1 = Vector3.Empty;
			Vector3 nextPos0 = Vector3.Empty;
			Vector3 nextPos1 = Vector3.Empty;

			while (iter.MoveNext())
			{
				if (last == null)
				{
					nextPos0 = new Vector3(iter.Current.X, iter.Current.Y,0);
					nextPos1 = new Vector3(iter.Current.X, iter.Current.Y,0);
					lastWasFirst = true;
				}
				else
				{
					Vector2 delta = new Vector2(iter.Current.X - last.X, iter.Current.Y - last.Y);
					float lengthRecip = 1.0f/delta.Length();
					delta = new Vector2(delta.X * lengthRecip, delta.Y * lengthRecip);

					if (lastWasFirst)
					{
						lastPos0 = new Vector3(last.X - (delta.Y * lineWidth), last.Y + (delta.X * lineWidth), 0);
						lastPos1 = new Vector3(last.X + (delta.Y * lineWidth), last.Y - (delta.X * lineWidth), 0);
						verts[index + 0] = new CustomVertex.PositionColored(lastPos0.X, lastPos0.Y, 0f, lastColour);
						verts[index + 1] = new CustomVertex.PositionColored(lastPos1.X, lastPos1.Y, 0f, lastColour);
						index += 2;
						lastWasFirst = false;
					}

					nextPos0 = new Vector3(iter.Current.X - (delta.Y * lineWidth), iter.Current.Y + (delta.X * lineWidth), 0);
					nextPos1 = new Vector3(iter.Current.X + (delta.Y * lineWidth), iter.Current.Y - (delta.X * lineWidth), 0);

					nextColour = iter.Current.Colour.ToArgb();
					lastColour = last.Colour.ToArgb();

					verts[index + 0] = new CustomVertex.PositionColored(nextPos0.X, nextPos0.Y, 0f, nextColour);
					verts[index + 1] = new CustomVertex.PositionColored(nextPos1.X, nextPos1.Y, 0f, nextColour);
					index += 2;
				}

				lastPos0 = nextPos0;
				lastPos1 = nextPos1;
				last = iter.Current;
			}

			VertexBuffer lineVertices = new VertexBuffer(
				typeof(CustomVertex.PositionColored), verts.Length, device, 0,
				CustomVertex.PositionColored.Format, Pool.Default
				);
			GraphicsStream stm = lineVertices.Lock(0, 0, 0);
			stm.Write(verts);
			lineVertices.Unlock();

			device.Transform.World = Matrix.Scaling(10f,10f,10f);
			device.VertexFormat = CustomVertex.PositionColored.Format;
			device.SetStreamSource(0, lineVertices, 0);
			device.SetTexture(0, null);
			device.Material = laserMaterial;
			device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, verts.Length-2  );

			device.RenderState.AlphaBlendEnable = false;
		}

		/// <summary>
		/// Renders the lines that make up the laser
		/// </summary>
		/// <param name="device">Device</param>
		public void Render(Device device)
		{
			// Slow as fuck

			device.RenderState.AlphaBlendEnable = true;
			device.RenderState.SourceBlend = Blend.One;
			device.RenderState.DestinationBlend = Blend.One;
			device.RenderState.BlendOperation = BlendOperation.Add;

			line.Width = lineWidth;
			line.Begin();

			bool lastSet = false;
			Vector3 last = new Vector3();
			Vector3[] vertices = new Vector3[2];
			Queue<LaserHistory>.Enumerator iter = history.GetEnumerator();
			while (iter.MoveNext())
			{
				Vector3 next = new Vector3(iter.Current.X, iter.Current.Y, 0);
				if (lastSet)
				{
					vertices[0] = last;
					vertices[1] = next;

					// HACK!  To stop the wrap around effect
					if ((next - last).Length() < 0.18f)
					{
						line.DrawTransform(vertices, Matrix.Identity, iter.Current.Colour.ToArgb());
					}
					else
					{
						// HACK - this deals with it.  
						float x = last.X;
						float y = last.Y;
						float addX = 0;
						float addY = 0;

						if (last.X > 0.9f && next.X < -0.9f)
						{
							x = last.X - 2.0f;
							addX = 2.0f;
						}
						if (last.X < -0.9f && next.X > 0.9f)
						{
							x = last.X + 2.0f;
							addX =- 2.0f;
						}
						if (last.Y > 0.9f && next.Y < -0.9f)
						{
							y = last.Y - 2.0f;
							addY = 2.0f;
						}
						if (last.Y < -0.9f && next.Y > 0.9f)
						{
							y = last.Y + 2.0f;
							addY = -2.0f;
						}

						if ((next - last).Length() > 0.18f)
						{
							Debug.Assert(true, "hmm");
						}

						last = new Vector3(x, y, 0);
						vertices[0] = last;
						vertices[1] = next;
						line.DrawTransform(vertices, Matrix.Identity, iter.Current.Colour.ToArgb());

						Vector3 newNext,newLast;
						if (addX != 0)
						{
							newNext = new Vector3(next.X + addX, next.Y, 0);
							newLast = new Vector3(last.X + addX, last.Y, 0);
							vertices[0] = newLast;
							vertices[1] = newNext;
							line.DrawTransform(vertices, Matrix.Identity, iter.Current.Colour.ToArgb());
						}
						if (addY != 0)
						{
							newNext = new Vector3(next.X, next.Y + addY, 0);
							newLast = new Vector3(last.X, last.Y + addY, 0);
							vertices[0] = newLast;
							vertices[1] = newNext;
							line.DrawTransform(vertices, Matrix.Identity, iter.Current.Colour.ToArgb());
						}
					}
				}
				last = next;
				lastSet = true;

				iter.Current.Fade(FadeScalar);
			}

			line.End();
			device.RenderState.AlphaBlendEnable = false;

		}

		/// <summary>
		/// Casts the matrix to the sphere and gets the UV map
		/// </summary>
		/// <param name="matrix"></param>
		/// <returns></returns>
		public Vector2 GetUV(Matrix matrix)
		{
			Vector3 vertexRay;
			Vector3 meshCenter = new Vector3(0, 0, 0);
			double phi;
			float u;
			float v;

			Vector3 north = new Vector3(0f, 1f, 0f);
			Vector3 equator = new Vector3(1f, 0f, 0f);
			Vector3 northEquatorCross = Vector3.Cross(north, equator);

			// Cast the look vector of the matrix and find where it
			// intersects the sphere.  Then create a new matrix from the centre
			// of the sphere to that point.  Use that to calculate the UV
			// position on the texture rather than the laser matrix.  This should
			// deal with lasers not positioned in the centre of the sphere.

			Vector3 top = new Vector3(matrix.M11, matrix.M12, matrix.M13);
			Vector3 up = new Vector3(matrix.M21, matrix.M22, matrix.M23);
			Vector3 look = new Vector3(matrix.M31, matrix.M32, matrix.M33);
			Vector3 translate = new Vector3(matrix.M41, matrix.M42, matrix.M43);

			Ray3D ray = new Ray3D(translate, look);

			Sphere3D sphere = new Sphere3D(
				new Vector3(0, 0, 0),
				Settings.Default.SphereRadius
				);

			float t = GeometryHelper.Intersection(ray, sphere);
			Vector3 point = look * t;

			// For each vertex take a ray from the centre of the mesh to the vertex 
			// and normalize so the dot products work.
			vertexRay = Vector3.Normalize(point - meshCenter);

			//// Ref:http://www.cs.unc.edu/~rademach/xroads-RT/RTarticle.html and
			//// Glassner, A. (ed) An Introduction to Ray Tracing. Academic Press New York, N.Y. 1989. 
			phi = Math.Acos((double)Vector3.Dot(north, vertexRay));
			v = (float)(phi / Math.PI);

			if (phi == 0.0) //if north and vertex ray are coincident then we can pick an arbitray u since its the entire top/bottom line of the texture
			{
			    u = 0.0f;
			}
			else
			{
			    // Clamp the acos() param to 1.0/-1.0 (rounding errors are sometimes taking it slightly over.
			    u = (float)(Math.Acos(Math.Max(Math.Min((double)Vector3.Dot(equator, vertexRay) / Math.Sin(phi), 1.0), -1.0)) / (2.0 * Math.PI));
			    if (Vector3.Dot(northEquatorCross, vertexRay) >= 0.0)
			    {
					u = 1 - u;
			    }
			}

			return new Vector2(u, v);
		}
	}
}